package se.webbzon.oschi01.worldobject.appearance;

import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.TriangleFanArray;
import javax.vecmath.Point3d;
import javax.vecmath.TexCoord2f;

import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;

public class ConeAppearance3D extends SimpleAppearance3D {

	// The closed geometry model of the cone
	private static final Geometry modelClosed = createModelClosed(1,0.5,18,false);
	
	// The open geometry model of the cone
	private static final Geometry modelOpen = createModelOpen(1,0.5,18,false);
	
	// The closed geometry model of the back-faced cone
	private static final Geometry modelBackfacedClosed = createModelClosed(1,0.5,18,true);
		
	// The open geometry model of the back-faced cone
	private static final Geometry modelBackfacedOpen = createModelOpen(1,0.5,18,false);

	/*============================================================
	Constructors
	============================================================*/
		
	/** Creates a new WorldObjectConeAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and the top will be placed in
	 * (0,0.5,0). The radius of the cone will be equal to 1. The number
	 * of divisions (sides) will be equal to 18. **/
	public ConeAppearance3D(boolean open) {
		this(0,0,open, false);
	}	
		
	/** Creates a new WorldObjectConeAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and the top will be placed in
	 * (0,0.5,0). The radius of the cone will be equal to 1. The number
	 * of divisions (sides) will be equal to 18. 
	 * See WorldObjectSimpleAppearance for full description of the
	 * attributes renderingOrder and simpleAppearanceProperties.
	 * The appearance may be back-faced. **/
	public ConeAppearance3D(int renderingOrder,
							int simpleAppearanceProperties, 
							boolean open, boolean backfaced) {
		super(renderingOrder, simpleAppearanceProperties,
				open ? (backfaced ? modelBackfacedOpen : modelOpen) : 
					(backfaced ? modelBackfacedClosed : modelClosed));
	}
	
	/** Creates a new WorldObjectConeAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and the top will be placed in
	 * (0,0.5,0). The radius of the cone will be equal to 1. The number
	 * of divisions (sides) is given by the input value. **/
	public ConeAppearance3D(boolean open, int divisions) {
		this(0,0,open,divisions, false);
	}
		
	/** Creates a new WorldObjectConeAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and the top will be placed in
	 * (0,0.5,0). The radius of the cone will be equal to 1. The number
	 * of divisions (sides) is given by the input value. 
	 * See WorldObjectSimpleAppearance for full description of the
	 * attributes renderingOrder and simpleAppearanceProperties. 
	 * The appearance may be back-faced.**/
	public ConeAppearance3D(int renderingOrder,
							int simpleAppearanceProperties,
							boolean open, int divisions, boolean backfaced) {
		super(	renderingOrder, simpleAppearanceProperties,
				open ? 	createModelOpen(1,0.5,divisions,backfaced) : 
						createModelClosed(1,0.5,divisions,backfaced));
	}
		
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Creates the closed geometry model. **/
	private static Geometry createModelClosed(	double height,
												double radius,
												int divisions,
												boolean backfaced) {
		final double h = height/2;
		final double dv = backfaced ? 2*Math.PI/divisions : -2*Math.PI/divisions;
		final TriangleFanArray ta = new TriangleFanArray(2*divisions+4,	
												GeometryArray.COORDINATES +
												GeometryArray.NORMALS + 
												GeometryArray.TEXTURE_COORDINATE_2,
												new int[] {divisions+2,divisions+2});
		
		// Create point and texture coordinate instance to save memory
		final Point3d point = new Point3d(0,h,0);
		final TexCoord2f texCoord = new TexCoord2f(0.5f,0.5f);
		
		// Create cone
		ta.setTextureCoordinate(0, 0, texCoord);
		ta.setCoordinate(0,point);
		
		ta.setTextureCoordinate(0, divisions+2, texCoord);
		point.set(0,-h,0);
		ta.setCoordinate(divisions+2,point);
		
		for (int i = 0; i <= divisions; i++) {
			final double cos = Math.cos(i*dv);
			final double sin = Math.sin(i*dv);
			final int shellIndex = i + 1;
			texCoord.set((float) (0.5+0.5*cos),(float) (0.5+0.5*sin));
			
			// Shell
			ta.setTextureCoordinate(0, shellIndex, texCoord);
			point.set(radius*cos,-h,radius*sin);
			ta.setCoordinate(shellIndex,point);
			
			final int baseIndex = divisions + i + 2;
			
			// Base
			ta.setTextureCoordinate(0, baseIndex, texCoord);
			point.setZ(-point.getZ());
			ta.setCoordinate(baseIndex,point);
		}
		
		// Generate normals
		final GeometryInfo g = new GeometryInfo(ta);
		final NormalGenerator ng = new NormalGenerator();
		ng.generateNormals(g);
		
		return g.getGeometryArray();
	}
	
	/** Creates the open geometry model. **/
	private static Geometry createModelOpen(	double height,
												double radius,
												int divisions,
												boolean backfaced) {
		final double h = height/2; 
		final double dv = backfaced ? 2*Math.PI/divisions : -2*Math.PI/divisions;
		final TriangleFanArray ta = new TriangleFanArray(divisions+2,	
												GeometryArray.COORDINATES +
												GeometryArray.NORMALS + 
												GeometryArray.TEXTURE_COORDINATE_2,
												new int[] {divisions+2});
		
		// Create point and texture coordinate instance to save memory
		final Point3d point = new Point3d(0,h,0);
		final TexCoord2f texCoord = new TexCoord2f(0.5f,0.5f);
		
		// Create cone
		ta.setTextureCoordinate(0, 0, texCoord);
		ta.setCoordinate(0,point);
		
		for (int i = 0; i <= divisions; i++) {
			final double cos = Math.cos(i*dv);
			final double sin = Math.sin(i*dv);
			texCoord.set((float) (0.5+0.5*cos),(float) (0.5+0.5*sin));
			
			// Shell
			ta.setTextureCoordinate(0, i+1, texCoord);
			point.set(radius*cos,-h,radius*sin);
			ta.setCoordinate(i+1,point);
		}
		
		// Generate normals
		final GeometryInfo g = new GeometryInfo(ta);
		final NormalGenerator ng = new NormalGenerator();
		ng.generateNormals(g);
		
		return g.getGeometryArray();
	}
	
}
